﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace JSIF_WCFServer.Misc.Logging
{
    /// <summary>
    /// A utility for logging that allows custom loggers to be added on at a future time without having to change code in every class that uses logging
    /// </summary>
    public class GenericLogger : ILogger
    {
        /// <summary>
        /// Adds a new logging target to the list of logging targets
        /// </summary>
        /// <remarks>
        /// new log messages will be directed to the new logger immedietly
        /// </remarks>
        /// <param name="loggerFactory">a factory which can create a logger</param>
        public static void AddLoggerTarget(ILoggerFactory loggerFactory)
        {
            lock (_factories)
            {
                _factories.Add(loggerFactory);
            }
        }
        /// <summary>
        /// Adds a new logging target to the list of logging targets
        /// </summary>
        /// <remarks>
        /// new log messages will be directed to the new logger immedietly
        /// </remarks>
        /// <param name="logger">a single logger object which can handle multiple loggers logging to it</param>
        public static void AddLoggerTarget(IMultiPurposeLogger logger)
        {
            lock (_multiPurposeLoggers)
            {
                _multiPurposeLoggers.Add(logger);
            }
        }
        private static List<ILoggerFactory> _factories = new List<ILoggerFactory>();
        private static List<IMultiPurposeLogger> _multiPurposeLoggers = new List<IMultiPurposeLogger>();

        /// <summary>
        /// Creates a logger with the specified name
        /// </summary>
        /// <param name="loggerName">the name of the logger</param>
        /// <returns>a new generic logger with the specified name</returns>
        public static GenericLogger GetLogger(String loggerName)
        {
            return new GenericLogger(loggerName);
        }

        /// <summary>
        /// Creates a logger using the class name as the name of the logger
        /// </summary>
        /// <typeparam name="T">Any class</typeparam>
        /// <returns>a logger using the class name as the name of the logger</returns>
        public static GenericLogger GetLogger<T>()
        {
            return new GenericLogger(typeof(T).Name);
        }

        private GenericLogger(String name)
        {
            _name = name;
        }

        private String _name;
        private List<ILogger> _targets = new List<ILogger>();
        /// <summary>
        /// Logs a message
        /// </summary>
        /// <param name="level">The severity level of the message</param>
        /// <param name="cause">The (optional) cause of the message</param>
        /// <param name="msg">The message to be logged</param>
        public void Log(ILogLevels level, Exception cause, String msg)
        {
            if (_targets.Count < _factories.Count)
            {
                lock (_targets)
                {
                    lock (_factories)
                    {
                        for (int i = _targets.Count; i < _factories.Count; i++)
                        {
                            _targets.Add(_factories[i].GetLogger(_name));
                        }
                    }
                }
            }
            foreach (ILogger target in _targets)
            {
                try { target.Log(level, cause, msg); }
                catch { }
            }
            try
            {
                foreach (IMultiPurposeLogger logger in _multiPurposeLoggers)
                {
                    try { logger.Log(_name, level, cause, msg); }
                    catch { }
                }
            }
            catch
            { }
        }

        private void preLog(ILogLevels level, Exception cause, String msg, params Object[] args)
        {
            if (msg != null && args != null && args.Length > 0)
            {
                Log(level, cause, String.Format(msg, args));
            }
            else
            {
                Log(level, cause, msg);
            }
        }

        /// <summary>
        /// Logs a debug level message
        /// </summary>
        /// <param name="msg">the message to log</param>
        /// <param name="args">optional arguments to String.format</param>
        public void Debug(string msg, params object[] args)
        {
            Debug(null, msg, args);
        }

        /// <summary>
        /// Logs an info level message
        /// </summary>
        /// <param name="msg">the message to log</param>
        /// <param name="args">optional arguments to String.format</param>
        public void Info(string msg, params object[] args)
        {
            Info(null, msg, args);
        }

        /// <summary>
        /// Logs a warning message
        /// </summary>
        /// <param name="msg">the message to log</param>
        /// <param name="args">optional arguments to String.format</param>
        public void Warn(string msg, params object[] args)
        {
            Warn(null, msg, args);
        }

        /// <summary>
        /// Logs an error message
        /// </summary>
        /// <param name="msg">the message to log</param>
        /// <param name="args">optional arguments to String.format</param>
        public void Error(string msg, params object[] args)
        {
            Error(null, msg, args);
        }

        /// <summary>
        /// Logs a fatal error message
        /// </summary>
        /// <param name="msg">the message to log</param>
        /// <param name="args">optional arguments to String.format</param>
        public void Fatal(string msg, params object[] args)
        {
            Fatal(null, msg, args);
        }

        /// <summary>
        /// Logs a debug message
        /// </summary>
        /// <param name="cause">An exception which caused the log</param>
        /// <param name="msg">the message to log</param>
        /// <param name="args">optional arguments to String.format</param>
        public void Debug(Exception cause, string msg, params object[] args)
        {
            preLog(ILogLevels.Debug, cause, msg, args);
        }

        /// <summary>
        /// Logs an info message
        /// </summary>
        /// <param name="cause">An exception which caused the log</param>
        /// <param name="msg">the message to log</param>
        /// <param name="args">optional arguments to String.format</param>
        public void Info(Exception cause, string msg, params object[] args)
        {
            preLog(ILogLevels.Info, cause, msg, args);
        }

        /// <summary>
        /// Logs a warning message
        /// </summary>
        /// <param name="cause">An exception which caused the log</param>
        /// <param name="msg">the message to log</param>
        /// <param name="args">optional arguments to String.format</param>
        public void Warn(Exception cause, string msg, params object[] args)
        {
            preLog(ILogLevels.Warn, cause, msg, args);
        }

        /// <summary>
        /// Logs an error message
        /// </summary>
        /// <param name="cause">An exception which caused the log</param>
        /// <param name="msg">the message to log</param>
        /// <param name="args">optional arguments to String.format</param>
        public void Error(Exception cause, string msg, params object[] args)
        {
            preLog(ILogLevels.Error, cause, msg, args);
        }

        /// <summary>
        /// Logs a fatal error message
        /// </summary>
        /// <param name="cause">An exception which caused the log</param>
        /// <param name="msg">the message to log</param>
        /// <param name="args">optional arguments to String.format</param>
        public void Fatal(Exception cause, string msg, params object[] args)
        {
            preLog(ILogLevels.Fatal, cause, msg, args);
        }
    }
}
